home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Info-Mac 3
/
Info_Mac_1994-01.iso
/
Development
/
Source
/
FloatWin Source
next >
Wrap
Text File
|
1993-06-18
|
15KB
|
317 lines
unit Floating;
{A set of routines to handle floating windows. For use with THINK Pascal 4.0}
{Written by F. Pottier, june 1993. E-mail : pottier@clipper.ens.fr on Internet}
{This file looks best when viewed with THINK Pascal's pretty printer. I use Geneva 9 and 4 space tabs.}
{Originally based on a set of C routines written by Patrick Doane (America OnLine : Patrick5) }
{Thanks go to him, and also to Troy Gaul for placing his Infinity Windoid in the public domain}
{This code is placed in the public domain. It may be used by anybody, for any purposes. It would be kind to give credits to me}
{as well as to the aforementioned people, though.}
{If you experience any problems, or have ideas for enhancements, tell me about it. I can't guarantee I'll spend much}
{time on it, since I'm not a professional programmer, but I'll take a look at it.}
{ --- Some general comments ------------------------------------------------------------------------------------------ }
{The Window Manager doesn't know about floating windows. To obtain floating windows in a program,}
{one has to handle them 'by hand', which means calling low-level Window Manager routines to place the windows}
{in their correct positions after each event.}
{The frequently used, and now to be avoided, Window Manager routines are :}
{ SelectWindow }
{ CloseWindow }
{ HideWindow }
{ ShowWindow }
{ DisposeWindow }
{ DragWindow }
{Those routines don't know about floating windows, so calling them would put the floating windows behind normal windows.}
{You also have to be careful with GetNewWindow. You can't call GetNewWindow(ID, wStorage, Pointer(-1)) because that}
{would put the new window in the front. Rather, we call GetNewWindow(ID, wStorage, nil) which places the window in the}
{back, and then call our home-made routine, SelectTheWindow.}
{At every point in the program, we maintain three global variables : topFloat, bottomFloat and topWindow.}
{topFloat holds the top floating window, nil if there is none. Same thing for bottomFloat with the bottom floating window,}
{and for topWindow with the top regular window. These three global variables, along with the Window Manager's window}
{list, are enough to handle the behavior of our windows.}
{Hidden windows are placed behind all others, so they don't count when determining those variables' values.}
{In order to distinguish between floating windows and regular ones, we choose a different windowKind for the former.}
{To make hiliting and unhiliting floating windows simpler, I chose to use a WDEF that always draws windoids in a hilited}
{state (e.g. Infinity Windoid with the Always Hilite option). This way, I don't have to worry at all about it.}
{A final remark about modeless dialogs : modeless dialogs and floating windows don't go well together. The Dialog Manager}
{is confused by floating windows. IsDialogEvent and DialogSelect don't work because they use FrontWindow to find the dialog.}
{It is easy to rewrite a modified version of isDialogEvent. It is already trickier for DialogSelect. Maybe it would be simpler}
{to use windows with controls instead of modeless dialogs. Or maybe it's possible to have DialogSelect and isDialogEvent}
{work by patching FrontWindow ? }
{If somebody comes up with a good idea on this topic, please tell me about it.}
{ --- How to use the unit, in short -------------------------------------------------------------------------------------- }
{- Call InitFloats once before opening any windows.}
{- To create a new window, call GetNewWindow(id, storage, NIL). Then call SelectTheWindow if it's a regular window,}
{ otherwise call MakeFloat.}
{- To determine whether a given window is a floating one, call isFloating}
{- Instead of SelectWindow[and ShowWindow], HideWindow, DisposeWindow, DragWindow, use SelectTheWindow, HideTheWindow,}
{ DIsposeTheWindow and DragTheWindow.}
{- Upon a receiving a suspend event, you may call HideFloats. Use ShowFloats when receiving a resume event.}
{- Instead of calling FrontWindow, you can read the values of the variables topFloat and topWindow. They should be up-to-date}
{ at any point in the program.}
{ --- Unit interface --------------------------------------------------------------------------------------------------- }
interface
procedure InitFloats; {initializes the global variables}
function IsFloating (whichWindow: WindowPtr): Boolean; {tells whether a window is floating}
procedure MakeFloat (theWindow: WindowPtr); {turns a newly created window into a floating window}
procedure HideTheWindow (whichWindow: WindowPtr); {equivalents for the old Toolbox calls}
procedure DisposeTheWindow (whichWindow: WindowPtr);
procedure SelectTheWindow (whichWindow: WindowPtr);
procedure DragTheWindow (whichWindow: WindowPtr; event: EventRecord);
procedure HideFloats; {hides all floating windows}
procedure ShowFloats; {shows all floating windows}
var
bottomFloat, topFloat, topWindow: WindowPtr;
{ -------------------------------------------------------------------------------------------------------------------- }
implementation
const
kFloatingKind = 317; {arbitrary constant for floating windows' windowKind}
{ActivateWindow hilites/unhilites the window and then generates an activate/deactivate event for it.}
{In order to generate activate events, it mucks with low-mem globals. It would be cleaner to directly call the activate}
{routine for the window. The advantage of this method is that it also works with modeless dialogs : the Dialog Manager}
{doesn't know that a dialog is active unless it actually receives an activate event.}
procedure ActivateWindow (window: WindowPtr; on: Boolean);
const
CurActivate = $0A64; {writing a WindowPtr in those globals generates}
CurDeactive = $0A68; {an activate or deactivate event}
var
p: ^WindowPtr;
begin
if window <> nil then begin
HiliteWindow(window, on); {hilite the window}
if on then
p := Pointer(CurActivate) {generate the appropriate event}
else
p := Pointer(CurDeactive);
p^ := window;
end;
end;
{InitFloats is to be called at the beginning of the program, before creating any windows. It sets our global variables to nil.}
procedure InitFloats;
begin
topFloat := nil;
bottomFloat := nil;
topWindow := nil;
end;
{IsFloating and isVisible are little, foolprof routines that tell whether a window is floating or visible.}
{Using them avoids errors such as examining WindowPeek(whichWindow)^.visible when whichWindow = nil}
function IsFloating (whichWindow: WindowPtr): Boolean;
begin
if whichWindow = nil then
IsFloating := false
else
IsFloating := WindowPeek(whichWindow)^.windowKind = kFloatingKind;
end;
function isVisible (whichWindow: WindowPtr): Boolean;
begin
if whichWindow = nil then
isVisible := false
else
isVisible := WindowPeek(whichWindow)^.visible;
end;
{UpdateFloats updates the topFloat and bottomFloat variables by walking down the window list. It then determines}
{topWindow by taking the first visible window after bottomFloat.}
procedure UpdateVars;
var
theWindow: WindowPtr;
begin
theWindow := FrontWindow; {start from the frontmost window}
if IsFloating(theWindow) then begin {if it's floating, then it's topFloat}
topFloat := theWindow;
while (theWindow <> nil) and isFloating(theWindow) do begin {walk down the list until we find a regular window}
bottomFloat := theWindow; {or the end of the list}
theWindow := WindowPtr(WindowPeek(theWindow)^.nextWindow);
end;
while (theWindow <> nil) and (not isVisible(theWindow)) do {find the next visible window after bottomFloat}
theWindow := WindowPtr(WindowPeek(theWindow)^.nextWindow);
topWindow := theWindow;
end
else begin {if the frontmost window is a regular one, then there}
topFloat := nil; {are no floating windows and topWindow = FrontWindow}
bottomFloat := nil;
topWindow := FrontWindow;
end;
end;
{MakeFloat turns a newly created window into a floating window and brings it to the front}
{The window must have been created via a call to [Get]NewWindow(id, wStorage, nil).}
procedure MakeFloat (theWindow: WindowPtr);
begin
BringToFront(theWindow); {Bring the window to the front without unhiliting other}
ShowHide(theWindow, true); {windows. Make it visible in case it isn't}
if topFloat = nil then
bottomFloat := theWindow; {update bottomFloat et topFloat}
topFloat := theWindow;
WindowPeek(theWindow)^.windowKind := kFloatingKind; {remember that this window should float}
end;
{ReactToRemoval handles a few necessary steps after killing whichWindow. underWindow is the window that was}
{under whichWindow (i.e. next to it in the window list).}
procedure ReactToRemoval (underWindow: WindowPeek);
begin
while (underWindow <> nil) and (not isVisible(WindowPtr(underWindow))) do
underWindow := underWindow^.nextWindow; {activate the next visible window under whichWindow}
ActivateWindow(WindowPtr(underWindow), true);
UpdateVars; {update the variables}
end;
procedure HideTheWindow (whichWindow: WindowPtr);
var
underWindow: WindowPeek; {we must determine which window is under whichWindow}
begin {before hiding it, because HideWindow sends it to the back}
underWindow := WindowPeek(whichWindow)^.nextWindow; {thus changing the value of nextWindow}
HideWindow(whichWindow);
ReactToRemoval(underWindow);
end;
procedure DisposeTheWindow (whichWindow: WindowPtr);
var
underWindow: WindowPeek;
begin
underWindow := WindowPeek(whichWindow)^.nextWindow; {same thing as HideTheWindow, with DisposeWindow instead}
DisposeWindow(whichWindow);
ReactToRemoval(underWindow);
end;
{SelectTheWindow is the most important routine. Here are the steps to be taken :}
{If whichWindow is not visible, show it.}
{- If whichWindow is a floating window, just bring it to the front.}
{- If it is a regular window and there are no floating windows, bring it to the front, activate it, deactivate the former front window}
{- If it is a regular window and there are floating windows, send it behind bottomFloat, activate it, deactivate the old topWindow}
{Finally, update the global variables.}
procedure SelectTheWindow (whichWindow: WindowPtr);
var
wPeek: WindowPeek;
begin
wPeek := WindowPeek(whichWindow);
if not wPeek^.visible then {make the window visible if necessary}
ShowHide(whichWindow, true);
if IsFloating(whichWindow) then {a floating window just needs to be brought to the front}
BringToFront(whichWindow)
else if whichWindow <> topWindow then begin {regular window. If it isn't topWindow we have to react}
if bottomFloat <> nil then begin {if there are floating windows, then send whichWindow}
SendBehind(whichWindow, bottomFloat); {behind bottomFloat}
PaintOne(wPeek, wPeek^.strucRgn); {These two low-level calls are necessary to generate}
CalcVis(wPeek); {update events and maintain the windows' visRgns}
end
else
BringToFront(whichWindow); {no floating windows : just call BringToFront}
ActivateWindow(whichWindow, true); {activate the new front window}
ActivateWindow(topWindow, false); {and deactivate the old one}
end;
UpdateVars; {finally update our vars}
end;
{DragTheWindow is to be called instead of DragWindow, because DragWindow brings the window to the front unless the command}
{key is down. We use DragGrayRgn to drag a gray outline of the window. We clip the dragging to a region consisting of the whole}
{desktop, minus the windows that are in front of the window we're dragging.}
{To emulate DragWindow's behavior, we send the window to the front, unless the command key is down.}
procedure DragTheWindow (whichWindow: WindowPtr; event: EventRecord);
const
cancelDrag = $80008000; {returned by DragGrayRgn if the user aborts the drag}
var
savePort: GrafPtr;
wPort: GrafPort;
thePoint: point;
newLoc: longint;
theWindow: WindowPeek;
dragRect: rect;
dragRgn: RgnHandle;
begin
if BitAnd(event.modifiers, cmdKey) = 0 then {unless the command key is depressed, select the window}
SelectTheWindow(whichWindow); {before doing the dragging}
GetPort(savePort); {save the grafport}
SetPort(whichWindow);
thePoint := whichWindow^.portRect.topLeft;
LocalToGlobal(thePoint); {remember the window's original position}
dragRgn := NewRgn; {put the window's strucRgn in a new region}
CopyRgn(WindowPeek(whichWindow)^.strucRgn, dragRgn); {we can't pass the strucRgn directly to DragGrayRgn}
{because DragGrayRgn modifies the region}
OpenPort(@wPort); {open a port to draw the gray outline}
CopyRgn(GetGrayRgn, wPort.visRgn); {set its visRgn to the whole screen, minus the strucRgns}
theWindow := WindowPeek(FrontWindow); {of all the windows in front of whichWindow}
while theWindow <> WindowPeek(whichWindow) do begin
DiffRgn(wPort.visRgn, theWindow^.strucRgn, wPort.visRgn);
theWindow := theWindow^.nextWindow;
end;
dragRect := GetGrayRgn^^.rgnBBox;
InsetRect(dragRect, 4, 4);
newLoc := DragGrayRgn(dragRgn, event.where, dragRect, dragRect, noConstraint, nil);
if (newLoc <> cancelDrag) and (newLoc <> 0) then
MoveWindow(whichWindow, thePoint.h + LoWord(newLoc), thePoint.v + HiWord(newLoc), false);
DisposeRgn(dragRgn); {dispose of the region and of the port}
ClosePort(@wPort);
SetPort(savePort); {then restore the old grafPort and exit}
end;
{HideFloats/ShowFloats are simple routines that hide/show all floating windows. They're useful to handle suspend/resume events.}
procedure HideFloats;
begin
while isFloating(FrontWindow) do {calling FrontWindow twice is not very efficient, but}
HideWindow(FrontWindow); {it's shorter!}
topFloat := nil;
bottomFloat := nil;
end;
procedure ShowFloats;
const
WindowList = $09D6; {this low-mem variable contains the address of the}
var {first window in the Window Manager's list}
theWindow, nextWindow: WindowPeek;
p: ^WindowPeek;
begin
p := Pointer(WindowList);
theWindow := WindowPeek(p^); {find the first window in the Window Manager's list}
while (theWindow <> nil) do begin {walk down the list...}
nextWindow := theWindow^.nextWindow;
if isFloating(WindowPtr(theWindow)) then
SelectTheWindow(WindowPtr(theWindow)); {..sending every floating window to the front}
theWindow := nextWindow;
end;
end;
end.